package org.examples.newfeatures.repository;

import com.google.common.base.Optional;
import org.examples.newfeatures.domain.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.QueryHint;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;


//及时跟踪本例子的变化，查看用法。
//同类的用法，已经去掉，仅保留经典方法。
/**
 * Repository interface for {@code User}s.
 *
 * @author Oliver Gierke
 * @author Thomas Darimont
 */
public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {

    /**
     * Retrieve users by their lastname. The finder {@literal User.findByLastname} is declared in
     * {@literal META-INF/orm.xml} .
     *
     * @param lastname
     * @return all users with the given lastname
     */
    @QueryHints({@QueryHint(name = "foo", value = "bar")})
    List<User> findByLastname(String lastname);

    /**
     * Redeclaration of {@link CrudRepository#findOne(java.io.Serializable)} to change transaction configuration.
     */
    @Transactional
    User findOne(Integer primaryKey);

    @Query("select u from User u left outer join u.manager as manager")
    Page<User> findAllPaged(Pageable pageable);

    /**
     * Retrieves users by the given email and lastname. Acts as a dummy method declaration to test finder query creation.
     *
     * @param emailAddress
     * @param lastname
     * @return the user with the given email address and lastname
     */
    User findByEmailAddressAndLastname(String emailAddress, String lastname);

    /**
     * Retrieves a user by its username using the query annotated to the method.
     *
     * @param username
     * @return
     */
    @Query("select u from User u where u.emailAddress = ?1")
    @Transactional(readOnly = true)
    User findByAnnotatedQuery(String emailAddress);

    /**
     * Method to directly create query from and adding a {@link Pageable} parameter to be regarded on query execution.
     *
     * @param pageable
     * @param lastname
     * @return
     */
    Page<User> findByLastname(Pageable pageable, String lastname);

    /**
     * Method to directly create query from and adding a {@link Pageable} parameter to be regarded on query execution.
     * Just returns the queried {@link Page}'s contents.
     *
     * @param firstname
     * @param pageable
     * @return
     */
    List<User> findByFirstname(String firstname, Pageable pageable);

    Page<User> findByFirstnameIn(Pageable pageable, String... firstnames);

    List<User> findByFirstnameNotIn(Collection<String> firstnames);

    /**
     * @see DATAJPA-292
     */
    @Query("select u from User u where u.firstname like ?1%")
    List<User> findByFirstnameLike(String firstname);

    /**
     * @see DATAJPA-292
     */
    @Query("select u from User u where u.firstname like :firstname%")
    List<User> findByFirstnameLikeNamed(@Param("firstname") String firstname);

    /**
     * Manipulating query to set all {@link User}'s names to the given one.
     *
     * @param lastname
     */
    @Modifying
    @Query("update User u set u.lastname = ?1")
    void renameAllUsersTo(String lastname);

    @Query("select count(u) from User u where u.firstname = ?1")
    Long countWithFirstname(String firstname);

    /**
     * Method where parameters will be applied by name. Note that the order of the parameters is then not crucial anymore.
     *
     * @param firstname
     * @param lastname
     * @return
     */
    @Query("select u from User u where u.lastname = :lastname or u.firstname = :firstname")
    List<User> findByLastnameOrFirstname(@Param("firstname") String foo, @Param("lastname") String bar);

    @Query("select u from User u where u.lastname = :lastname or u.firstname = :firstname")
    List<User> findByLastnameOrFirstnameUnannotated(String firstname, String lastname);

    /**
     * Method to check query creation and named parameter usage go well hand in hand.
     *
     * @param lastname
     * @param firstname
     * @return
     */
    List<User> findByFirstnameOrLastname(@Param("lastname") String lastname, @Param("firstname") String firstname);

    List<User> findByLastnameLikeOrderByFirstnameDesc(String lastname);



    @Query("select u.lastname from User u group by u.lastname")
    Page<String> findByLastnameGrouped(Pageable pageable);

    /**
     * @see DATAJPA-117
     */
    @Query(value = "SELECT * FROM User WHERE lastname = ?1", nativeQuery = true)
    List<User> findNativeByLastname(String lastname);



    /**
     * @see DATAJPA-188
     */
    List<User> findByCreatedAtAfter(Date date);

    /**
     * @see DATAJPA-180
     */
    List<User> findByFirstnameStartingWith(String firstname);



    @Query(value = "SELECT 1 FROM User", nativeQuery = true)
    List<Integer> findOnesByNativeQuery();


    /**
     * @see DATAJPA-231
     */
    int countUsersByFirstname(String firstname);

    /**
     * @see DATAJPA-391
     */
    @Query("select u.firstname from User u where u.lastname = ?1")
    List<String> findFirstnamesByLastname(String lastname);

    /**
     * @see DATAJPA-415
     */
    Collection<User> findByIdIn(@Param("ids") Integer... ids);

    /**
     * @see DATAJPA-461
     */
    @Query("select u from User u where u.id in ?1")
    Collection<User> findByIdsCustomWithPositionalVarArgs(Integer... ids);

    /**
     * @see DATAJPA-461
     */
    @Query("select u from User u where u.id in :ids")
    Collection<User> findByIdsCustomWithNamedVarArgs(@Param("ids") Integer... ids);

    /**
     * @see DATAJPA-415
     */
    @Modifying
    @Query("update #{#entityName} u set u.active = :activeState where u.id in :ids")
    void updateUserActiveState(@Param("activeState") boolean activeState, @Param("ids") Integer... ids);

    /**
     * @see DATAJPA-405
     */
    List<User> findAllByOrderByLastnameAsc();

    /**
     * @see DATAJPA-454
     */
    List<User> findByBinaryData(byte[] data);

    /**
     * @see DATAJPA-486
     */
    Slice<User> findSliceByLastname(String lastname, Pageable pageable);

    /**
     * @see DATAJPA-496
     */
    List<User> findByAttributesIn(Set<String> attributes);

    /**
     * @see DATAJPA-460
     */
    Long removeByLastname(String lastname);

    /**
     * @see DATAJPA-460
     */
    List<User> deleteByLastname(String lastname);

    /**
     * @see DATAJPA-505
     * @see https://issues.apache.org/jira/browse/OPENJPA-2484
     */
    // @Query(value = "select u.binaryData from User u where u.id = :id")
    // byte[] findBinaryDataByIdJpaQl(@Param("id") Integer id);

    /**
     * Explicitly mapped to a procedure with name "plus1inout" in database.
     *
     * @see DATAJPA-455
     */
    @Procedure("plus1inout")
    Integer explicitlyNamedPlus1inout(Integer arg);

    /**
     * Implicitly mapped to a procedure with name "plus1inout" in database via alias.
     *
     * @see DATAJPA-455
     */
    @Procedure(procedureName = "plus1inout")
    Integer plus1inout(Integer arg);

    /**
     * Explicitly mapped to named stored procedure "User.plus1IO" in {@link EntityManager}.
     *
     * @see DATAJPA-455
     */
    @Procedure(name = "User.plus1IO")
    Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);

    /**
     * Implicitly mapped to named stored procedure "User.plus1" in {@link EntityManager}.
     *
     * @see DATAJPA-455
     */
    @Procedure
    Integer plus1(@Param("arg") Integer arg);

    /**
     * @see DATAJPA-456
     */
    @Query(value = "select u from User u where u.firstname like ?1%", countProjection = "u.firstname")
    Page<User> findAllByFirstnameLike(String firstname, Pageable page);

    /**
     * @see DATAJPA-456
     */
    @Query(name = "User.findBySpringDataNamedQuery", countProjection = "u.firstname")
    Page<User> findByNamedQueryAndCountProjection(String firstname, Pageable page);


    /**
     * @see DATAJPA-551
     */
    List<User> findTop2UsersBy(Sort sort);

    /**
     * @see DATAJPA-551
     */
    Page<User> findFirst3UsersBy(Pageable page);



    /**
     * @see DATAJPA-506
     */
    @Query(value = "select u.binaryData from User u where u.id = ?1", nativeQuery = true)
    byte[] findBinaryDataByIdNative(Integer id);

    /**
     * @see DATAJPA-506
     */
    @Query("select u from User u where u.emailAddress = ?1")
    Optional<User> findOptionalByEmailAddress(String emailAddress);

    /**
     * @see DATAJPA-564
     */
    @Query("select u from User u where u.firstname = ?#{[0]} and u.firstname = ?1 and u.lastname like %?#{[1]}% and u.lastname like %?2%")
    List<User> findByFirstnameAndLastnameWithSpelExpression(String firstname, String lastname);

    /**
     * @see DATAJPA-564
     */
    @Query("select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%")
    List<User> findByLastnameWithSpelExpression(@Param("lastname") String lastname);

    /**
     * @see DATAJPA-564
     */
    @Query("select u from User u where u.firstname = ?#{'Oliver'}")
    List<User> findOliverBySpELExpressionWithoutArgumentsWithQuestionmark();

    /**
     * @see DATAJPA-564
     */
    @Query("select u from User u where u.firstname = :#{'Oliver'}")
    List<User> findOliverBySpELExpressionWithoutArgumentsWithColon();

    /**
     * @see DATAJPA-564
     */
    @Query("select u from User u where u.age = ?#{[0]}")
    List<User> findUsersByAgeForSpELExpressionByIndexedParameter(int age);

    /**
     * @see DATAJPA-564
     */
    @Query("select u from User u where u.firstname = :firstname and u.firstname = :#{#firstname}")
    List<User> findUsersByFirstnameForSpELExpression(@Param("firstname") String firstname);

    /**
     * @see DATAJPA-564
     */
    @Query("select u from User u where u.emailAddress = ?#{principal.emailAddress}")
    List<User> findCurrentUserWithCustomQuery();

    /**
     * @see DATAJPA-564
     */
    @Query("select u from User u where u.firstname = ?1 and u.firstname=?#{[0]} and u.emailAddress = ?#{principal.emailAddress}")
    List<User> findByFirstnameAndCurrentUserWithCustomQuery(String firstname);

    /**
     * @see DATAJPA-564
     */
    @Query("select u from User u where u.firstname = :#{#firstname}")
    List<User> findUsersByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname);

    /**
     * @see DATAJPA-564
     */
    @Query("select u from User u where u.firstname = ?#{[0]}")
    List<User> findUsersByFirstnameForSpELExpressionWithParameterIndexOnly(String firstname);

    /**
     * @see DATAJPA-564
     */
    @Query(
            value = "select * from (select rownum() as RN, u.* from User u) where RN between ?#{ #pageable.offset -1} and ?#{#pageable.offset + #pageable.pageSize}",
            countQuery = "select count(u.id) from User u", nativeQuery = true)
    Page<User> findUsersInNativeQueryWithPagination(Pageable pageable);

    /**
     * @see DATAJPA-629
     */
    @Query("select u from #{#entityName} u where u.firstname = ?#{[0]} and u.lastname = ?#{[1]}")
    List<User> findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntityExpression(String firstname,
                                                                                               String lastname);

    /**
     * DATAJPA-606
     */
    List<User> findByAgeIn(Collection<Integer> ages);

    /**
     * DATAJPA-606
     */
    List<User> queryByAgeIn(Integer[] ages);

    /**
     * DATAJPA-606
     */
    List<User> queryByAgeInOrFirstname(Integer[] ages, String firstname);

    /**
     * DATAJPA-677  Stream  jdk8
     */
 //   @Query("select u from User u")
 //   Stream<User> findAllByCustomQueryAndStream();

    /**
     * DATAJPA-677   Stream  jdk8
     */
 //   Stream<User> readAllByFirstnameNotNull();

    /**
     * DATAJPA-677    Stream  jdk8
     */
 //   @Query("select u from User u")
//    Stream<User> streamAllPaged(Pageable pageable);
}